// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:io';

import 'package:args/args.dart';
import 'package:expect/async_helper.dart';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/dart2js.dart' as entry;

import 'package:sourcemap_testing/src/stacktrace_helper.dart';

import 'package:compiler/src/util/memory_compiler.dart';

void main(List<String> args) {
  ArgParser argParser = ArgParser(allowTrailingOptions: true);
  argParser.addFlag('write-js', defaultsTo: false);
  argParser.addFlag('print-js', defaultsTo: false);
  argParser.addFlag('verbose', abbr: 'v', defaultsTo: false);
  argParser.addFlag('continued', abbr: 'c', defaultsTo: false);
  ArgResults argResults = argParser.parse(args);
  Directory dataDir = Directory.fromUri(Platform.script.resolve('stacktrace'));
  asyncTest(() async {
    bool continuing = false;
    await for (FileSystemEntity entity in dataDir.list()) {
      String name = entity.uri.pathSegments.last;
      if (!name.endsWith('.dart')) continue;
      if (argResults.rest.isNotEmpty &&
          !argResults.rest.contains(name) &&
          !continuing) {
        continue;
      }
      print('----------------------------------------------------------------');
      print('Checking ${entity.uri}');
      print('----------------------------------------------------------------');
      String annotatedCode = await File.fromUri(entity.uri).readAsString();
      await testAnnotatedCode(
        annotatedCode,
        verbose: argResults['verbose'],
        printJs: argResults['print-js'],
        writeJs: argResults['write-js'],
        inlineData: name.contains('_inlining'),
      );
      if (argResults['continued']) {
        continuing = true;
      }
    }
  });
}

const String kernelMarker = 'kernel.';

Future testAnnotatedCode(
  String code, {
  bool printJs = false,
  bool writeJs = false,
  bool verbose = false,
  bool inlineData = false,
}) async {
  Test test = processTestCode(code, [kernelMarker]);
  print(test.code);
  print('---from kernel------------------------------------------------------');
  await runTest(
    test,
    kernelMarker,
    printJs: printJs,
    writeJs: writeJs,
    verbose: verbose,
    inlineData: inlineData,
  );
}

Future runTest(
  Test test,
  String config, {
  bool printJs = false,
  bool writeJs = false,
  bool verbose = false,
  bool inlineData = false,
  List<String> options = const <String>[],
}) async {
  List<LineException> testAfterExceptions = <LineException>[];
  if (config == kernelMarker) {
    for (LineException exception in afterExceptions) {
      testAfterExceptions.add(exception);
    }
  } else {
    testAfterExceptions = afterExceptions;
  }
  return testStackTrace(
    test,
    config,
    (String input, String output) async {
      List<String> arguments = [
        '-o$output',
        '--platform-binaries=$sdkPlatformBinariesPath',
        '--libraries-spec=$sdkLibrariesSpecificationPath',
        '--packages=${Platform.packageConfig}',
        Flags.testMode,
        input,
      ]..addAll(options);
      print("Compiling dart2js ${arguments.join(' ')}");
      CompilationResult compilationResult = await entry.internalMain(arguments);
      return compilationResult.isSuccess;
    },
    jsPreambles:
        (input, output) => [
          '$sdkPath/_internal/js_runtime/lib/preambles/d8.js',
        ],
    afterExceptions: testAfterExceptions,
    beforeExceptions: beforeExceptions,
    verbose: verbose,
    printJs: printJs,
    writeJs: writeJs,
    stackTraceLimit: 100,
    expandDart2jsInliningData: inlineData,
  );
}

/// Lines allowed before the intended stack trace. Typically from helper
/// methods.
const List<LineException> beforeExceptions = const [
  const LineException('wrapException', 'js_helper.dart'),
  const LineException('throwExpression', 'js_helper.dart'),
  const LineException('throw_', 'js_helper.dart'),
];

/// Lines allowed after the intended stack trace. Typically from the event
/// queue.
const List<LineException> afterExceptions = const [
  const LineException('_asyncStartSync', 'async_patch.dart'),
  const LineException('_wrapJsFunctionForAsync', 'async_patch.dart'),
  const LineException(
    '_wrapJsFunctionForAsync.<anonymous function>',
    'async_patch.dart',
  ),
  const LineException(
    '_awaitOnObject.<anonymous function>',
    'async_patch.dart',
  ),
  const LineException('_asyncAwait.<anonymous function>', 'async_patch.dart'),
  const LineException('_asyncStart.<anonymous function>', 'async_patch.dart'),
  const LineException('_RootZone.runUnary', 'zone.dart'),
  const LineException('_FutureListener.handleValue', 'future_impl.dart'),
  const LineException(
    '_Future._asyncCompleteWithValue.<anonymous function>',
    'future_impl.dart',
  ),
  const LineException('_Future._completeWithValue', 'future_impl.dart'),
  const LineException(
    '_Future._propagateToListeners.handleValueCallback',
    'future_impl.dart',
  ),
  const LineException('_Future._propagateToListeners', 'future_impl.dart'),
  const LineException(
    '_Future._addListener.<anonymous function>',
    'future_impl.dart',
  ),
  const LineException('_microtaskLoop', 'schedule_microtask.dart'),
  const LineException('_startMicrotaskLoop', 'schedule_microtask.dart'),
  const LineException(
    '_AsyncRun._scheduleImmediateJsOverride.internalCallback',
    'async_patch.dart',
  ),
  const LineException('invokeClosure.<anonymous function>', 'js_helper.dart'),
  const LineException('invokeClosure', 'js_helper.dart'),
  const LineException('convertDartClosureToJS', 'js_helper.dart'),
];
